﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using System.Text.RegularExpressions;

[assembly: System.Security.AllowPartiallyTrustedCallersAttribute()]
namespace Search.PageAdapters.Utilities
{
    public class SearchAdapters
    {
        // Page Layouts to fix
        public static string[] searchLayoutsToFix = { "AdvancedSearchLayout.aspx", "SearchMain.aspx", "SearchResults.aspx", "PeopleSearchResults.aspx" };

        // Basic Search center pages to fix
        public static string[] basicSearchPagesToFix = { "default.aspx", "results.aspx", "advanced.aspx" };

        // PlaceHolders to fix or use in the fix
        public const string breadcrumbPlaceHolderStart = "<asp:Content ContentPlaceHolderID=\"PlaceHolderTitleBreadcrumb\" runat=\"server\">";
        public const string mainPlaceHolderStart = "<asp:Content ContentPlaceHolderID=\"PlaceHolderMain\" runat=\"server\">";
        public const string navigationPlaceHolderStart = "<asp:Content ContentPlaceHolderID=\"SPNavigation\" runat=\"server\">";
        public const string contentPlaceHolderEnd = "</asp:Content>";

        // PlaceHolder IDs to fix for the custom placeholder search fix
        public const string breadcrumbPlaceHolderID = "ContentPlaceHolderID=\"PlaceHolderTitleBreadcrumb\"";
        public const string customSearchPlaceHolderID = "ContentPlaceHolderID=\"PlaceHolderSearchControls\"";

        // CSS to hide the left navigation + breadcrumb that is not used by search layouts and basic search pages
        public const string cssToHideUnusedElements = "#s4-leftpanel, .s4-ca .s4-titletext {  display:none; }\r\n.s4-ca {  margin-left: 5px;  background: transparent; min-height: 144px; } .srch-sb-main { padding-top: 120px; }";
        public const string styleEndTag = "</style>";

        // Property key to track if change has been applied
        public const string searchFixKey = "_searchfix_applied";
        public const string searchFixFileBackupSuffix = "-searchFixBackup.aspx";

        public static void ApplyCustomSearchFix(SPWeb web, bool isActivation)
        {
            try
            {
                if (web != null)
                {
                    // Handle root web situation
                    string serverRelativeUrlPrefix = "";
                    if (web.ServerRelativeUrl == "/")
                        serverRelativeUrlPrefix = "";
                    else
                        serverRelativeUrlPrefix = web.ServerRelativeUrl;

                    // Site has page layouts
                    string layoutPageUrlPrefix = serverRelativeUrlPrefix + "/_catalogs/masterpage/";
                    foreach (string searchPageLayout in searchLayoutsToFix)
                    {
                        ApplyCustomSearchFixToFile(web, layoutPageUrlPrefix + searchPageLayout, isActivation);
                    }
                }
            }
            catch (Exception e)
            {
                throw new Exception(string.Format("There was an error while " + (isActivation ? "activating" : "deactivating") + " the search layout adapter"), e);
            }
        }

        private static void ApplyCustomSearchFixToFile(SPWeb web, string fileUrl, bool isActivation)
        {
            SPFile layoutOrPage = null;
            // try get a search page layout but if it doesnt exist dont worry about it
            try
            {
                // Changed from using SPWeb.GetFile to SPWeb.GetFileOrFolderObject as exceptions were getting throws
                // Reference: http://blog.mastykarz.nl/inconvenient-spwebgetfilestring/
                object _file = web.GetFileOrFolderObject(fileUrl);
                if (_file != null && _file is SPFile)
                    layoutOrPage = (SPFile)_file;
            }
            catch { }

            if (layoutOrPage != null)
            {
                ApplyCustomSearchFixToFile(layoutOrPage, isActivation);
            }
        }

        private static void ApplyCustomSearchFixToFile(SPFile layoutOrPage, bool isActivation)
        {
            if (isActivation)
            {
                bool searchFixApplied = false;

                // if this is a page layout get the property bag from the item to store custom properties
                if (layoutOrPage.InDocumentLibrary && layoutOrPage.Item != null)
                {
                    object flag = layoutOrPage.Item.Properties[searchFixKey];
                    if (flag != null)
                    {
                        searchFixApplied = bool.Parse((string)flag);
                    }
                }

                if (!searchFixApplied)
                {
                    // Get the contents of the page layout and change the ContentPlaceHolder the search web parts are in
                    string fileContents = Encoding.UTF8.GetString(layoutOrPage.OpenBinary());
                    int breadcrumbPHLayoutIndex = fileContents.IndexOf(breadcrumbPlaceHolderID, StringComparison.InvariantCultureIgnoreCase);
                    if (breadcrumbPHLayoutIndex > -1)
                    {
                        string layoutStart = fileContents.Substring(0, breadcrumbPHLayoutIndex);
                        StringBuilder newFileContents = new StringBuilder();

                        // Start the pagelayout and add CSS if its possible
                        int styleEndTagIndex = layoutStart.IndexOf(styleEndTag, StringComparison.InvariantCultureIgnoreCase);
                        if (styleEndTagIndex > -1)
                        {
                            // add custom CSS to the page layout if there is a style tag to host it
                            newFileContents.AppendLine(layoutStart.Substring(0, styleEndTagIndex));
                            newFileContents.AppendLine(cssToHideUnusedElements);
                            newFileContents.Append(layoutStart.Substring(styleEndTagIndex));
                        }
                        else
                            newFileContents.Append(layoutStart);

                        // replace the breadcrumb                 
                        newFileContents.Append(customSearchPlaceHolderID);

                        // Remove the extra SPNavigation breadcrumb
                        Regex extraRibbon = new Regex(navigationPlaceHolderStart + ".*" + contentPlaceHolderEnd, RegexOptions.IgnoreCase | RegexOptions.Singleline);
                        if (extraRibbon.IsMatch(fileContents.Substring(breadcrumbPHLayoutIndex + breadcrumbPlaceHolderID.Length)))
                        {
                            newFileContents.Append(extraRibbon.Replace(fileContents.Substring(breadcrumbPHLayoutIndex + breadcrumbPlaceHolderID.Length), string.Empty));
                        }
                        else
                        {
                            newFileContents.Append(fileContents.Substring(breadcrumbPHLayoutIndex + breadcrumbPlaceHolderID.Length));
                        }

                        // convert the new contents to it can be saved back to the layout page
                        byte[] modifiedFileContents = Encoding.UTF8.GetBytes(newFileContents.ToString());

                        if (layoutOrPage.InDocumentLibrary)
                        {
                            // Check out the page. If its already checked out then a useful exception should be thrown
                            layoutOrPage.CheckOut();
                        }

                        // Replace the contents of the page layout with the new improved version
                        layoutOrPage.SaveBinary(modifiedFileContents);

                        if (layoutOrPage.InDocumentLibrary)
                        {
                            if (layoutOrPage.Item != null)
                            {
                                // Add a custom property to tell us we have made the search fix change
                                layoutOrPage.Item.Properties[searchFixKey] = true;
                                layoutOrPage.Item.SystemUpdate();
                            }

                            // Check in, publish and approve so the changes are visible to the users
                            layoutOrPage.CheckIn("Page layout was modified by Search Fix feature.");
                            layoutOrPage.Publish("Page layout was modified by Search Fix feature.");
                            layoutOrPage.Approve("Page layout was modified by Search Fix feature.");
                        }
                    }
                }
            }
            else
            {
                // Rehost / Uncustomise the page layout
                if (layoutOrPage.CustomizedPageStatus == SPCustomizedPageStatus.Customized)
                    layoutOrPage.RevertContentStream();

                // Remove the custom property to tell us we have turned off the search fix change
                if (layoutOrPage.Item != null)
                {
                    layoutOrPage.Item.Properties[searchFixKey] = null;
                    layoutOrPage.Item.SystemUpdate();
                }
            }
        }

        public static void ApplyV4SearchFixToEnterpriseSearchCenter(SPSite site, bool isActivation)
        {
            try
            {
                if (site != null)
                {
                    // Get the root web as that will have the master page gallery
                    SPWeb rootWeb = site.RootWeb;

                    // Handle root web situation
                    string serverRelativeUrlPrefix = "";
                    if (rootWeb.ServerRelativeUrl == "/")
                        serverRelativeUrlPrefix = "";
                    else
                        serverRelativeUrlPrefix = rootWeb.ServerRelativeUrl;

                    // Assume the site has page layouts
                    string layoutPageUrlPrefix = serverRelativeUrlPrefix + "/_catalogs/masterpage/";
                    foreach (string searchPageLayout in searchLayoutsToFix)
                    {
                        ApplyV4SearchFixToFile(rootWeb, layoutPageUrlPrefix + searchPageLayout, isActivation);
                    }
                }

            }
            catch (Exception e)
            {
                throw new Exception(string.Format("There was an error while " + (isActivation ? "activating" : "deactivating") + " the search layout adapter"), e);
            }
        }

        public static void ApplyV4SearchFixToBasicSearchCenter(SPWeb web, bool isActivation)
        {
            try
            {
                if (web != null)
                {
                    // Handle root web situation
                    string serverRelativeUrlPrefix = "";
                    if (web.ServerRelativeUrl == "/")
                        serverRelativeUrlPrefix = "";
                    else
                        serverRelativeUrlPrefix = web.ServerRelativeUrl;

                    foreach (string searchPage in basicSearchPagesToFix)
                    {
                        ApplyV4SearchFixToFile(web, serverRelativeUrlPrefix + "/" + searchPage, isActivation);
                    }

                }
            }
            catch (Exception e)
            {
                throw new Exception(string.Format("There was an error while " + (isActivation ? "activating" : "deactivating") + " the search layout adapter"), e);
            }
        }

        private static void ApplyV4SearchFixToFile(SPWeb web, string fileUrl, bool isActivation)
        {
            SPFile layoutOrPage = null;
            // try get a search page layout but if it doesnt exist dont worry about it
            try
            {
                // Changed from using SPWeb.GetFile to SPWeb.GetFileOrFolderObject as exceptions were getting throws
                // Reference: http://blog.mastykarz.nl/inconvenient-spwebgetfilestring/
                object _file = web.GetFileOrFolderObject(fileUrl);
                if (_file != null && _file is SPFile)
                    layoutOrPage = (SPFile)_file;
            }
            catch { }

            if (layoutOrPage != null)
            {
                ApplyV4SearchFixToFile(layoutOrPage, isActivation);
            }
        }
        
        private static void ApplyV4SearchFixToFile(SPFile layoutOrPage, bool isActivation)
        {
            if (isActivation)
            {
                bool searchFixApplied = false;
                try
                {
                    // if this is a page layout get the property bag from the item to store custom properties
                    if (layoutOrPage.InDocumentLibrary && layoutOrPage.Item != null)
                    {
                        object flag = layoutOrPage.Item.Properties[searchFixKey];
                        if (flag != null)
                        {
                            searchFixApplied = bool.Parse((string)flag);
                        }
                    }
                }
                catch (Exception e)
                {
                }

                if (!searchFixApplied)
                {
                    // make a backup of the file if it a page and not a page layout. Assume we will overwrite any old backups at the same url
                    if (!layoutOrPage.InDocumentLibrary)
                    {
                        layoutOrPage.CopyTo(layoutOrPage.Name.Substring(0, layoutOrPage.Name.Length - 5) + searchFixFileBackupSuffix, true);
                    }

                    // Get the contents of the page layout and change the ContentPlaceHolder the search web parts are in
                    string fileContents = Encoding.UTF8.GetString(layoutOrPage.OpenBinary());

                    // RegEx expressions to find parts of the page we want to fix up
                    Regex extraRibbon = new Regex(navigationPlaceHolderStart + ".*" + contentPlaceHolderEnd, RegexOptions.IgnoreCase | RegexOptions.Singleline);
                    Regex breadCrumbContentRegex = new Regex(breadcrumbPlaceHolderStart.Replace(" ", @"\s+") + "(.*?)" + contentPlaceHolderEnd, RegexOptions.IgnoreCase | RegexOptions.Singleline);

                    Match match = breadCrumbContentRegex.Match(fileContents);
                    if (match.Success)
                    {
                        GroupCollection groups = match.Groups;
                        StringBuilder newFileContents = new StringBuilder();

                        if (groups.Count == 2)
                        {
                            // Get the contents of PlaceHolderTitleBreadcrumb so we can add it into another placeholder in the page 
                            string wholeMatch = groups[0].Value;
                            string breadCrumbContent = groups[1].Value;

                            // Remove the PlaceHolderTitleBreadcrumb placeholder
                            string tempFileContents = breadCrumbContentRegex.Replace(fileContents, string.Empty);

                            // Remove the extra SPNavigation breadcrumb
                            if (extraRibbon.IsMatch(tempFileContents))
                            {
                                tempFileContents = extraRibbon.Replace(tempFileContents, string.Empty);
                            }

                            // Start the pagelayout and add CSS if its possible
                            int styleEndTagIndex = tempFileContents.IndexOf(styleEndTag);
                            if (styleEndTagIndex > -1)
                            {
                                // add custom CSS to the page layout if there is a style tag to host it
                                tempFileContents = tempFileContents.Substring(0, styleEndTagIndex) +
                                    cssToHideUnusedElements +
                                    tempFileContents.Substring(styleEndTagIndex);
                            }

                            // Add the captured content into the main placeholder
                            int mainPlaceHolderIndex = tempFileContents.IndexOf(mainPlaceHolderStart);
                            if (mainPlaceHolderIndex > -1)
                            {
                                newFileContents.AppendLine(tempFileContents.Substring(0, mainPlaceHolderIndex + mainPlaceHolderStart.Length));
                                newFileContents.AppendLine(breadCrumbContent);
                                newFileContents.Append(tempFileContents.Substring(mainPlaceHolderIndex + mainPlaceHolderStart.Length));
                            }

                            // convert the new contents to it can be saved back to the layout page
                            byte[] modifiedFileContents = Encoding.UTF8.GetBytes(newFileContents.ToString());

                            if (layoutOrPage.InDocumentLibrary)
                            {
                                // Check out the page. If its already checked out then a useful exception should be thrown
                                layoutOrPage.CheckOut();
                            }

                            // Replace the contents of the page layout with the new improved version
                            layoutOrPage.SaveBinary(modifiedFileContents);

                            if (layoutOrPage.InDocumentLibrary)
                            {
                                if (layoutOrPage.Item != null)
                                {
                                    // Add a custom property to tell us we have made the search fix change
                                    layoutOrPage.Item.Properties[searchFixKey] = true;
                                    layoutOrPage.Item.SystemUpdate();
                                }

                                // Check in, publish and approve so the changes are visible to the users
                                layoutOrPage.CheckIn("Page layout was checked-in by Search Fix feature.");
                                layoutOrPage.Publish("Page layout was published by Search Fix feature.");
                                layoutOrPage.Approve("Page layout was approved by Search Fix feature.");
                            }
                        }
                    }
                }
            }
            else
            {
                // This is a basic search site so we need to restore the old pages from our backup. 
                // Check for backup pages and replace the existing ones if possible
                if (!layoutOrPage.InDocumentLibrary)
                {
                    // The page may not exist so we dont want an exception to stop processing
                    try
                    {
                        // Get the parent web from the file and then look for the backup page
                        SPWeb parentWeb = layoutOrPage.Web;
                        // Handle root web situation
                        string serverRelativeUrlPrefix = "";
                        if (parentWeb.ServerRelativeUrl == "/")
                            serverRelativeUrlPrefix = "";
                        else
                            serverRelativeUrlPrefix = parentWeb.ServerRelativeUrl;

                        object _fileObject = parentWeb.GetFileOrFolderObject(serverRelativeUrlPrefix + "/" + layoutOrPage.Name.Substring(0, layoutOrPage.Name.Length - 5) + searchFixFileBackupSuffix);
                        if (_fileObject != null && _fileObject is SPFile)
                        {
                            SPFile backupPage = (SPFile)_fileObject;
                            backupPage.CopyTo(layoutOrPage.Name, true);
                        }
                    }
                    catch (Exception e)
                    {
                        string message = e.Message;
                    }
                }
                else if (layoutOrPage.CustomizedPageStatus == SPCustomizedPageStatus.Customized)
                {
                    // Rehost / Uncustomise the page layout
                    layoutOrPage.RevertContentStream();
                }

                // Remove the custom property to tell us we have turned off the search fix change
                if (layoutOrPage.Item != null)
                {                    
                    layoutOrPage.Item.Properties[searchFixKey] = null;
                    layoutOrPage.Item.SystemUpdate();
                }
            }
        }
    }
}
